package busi

import (
	"fmt"
	htbsc "gitee.com/hangzhou_huitian/ht-basic/basic"
	fus "gitee.com/hangzhou_huitian/ht-basic/fusion"
	htutil "gitee.com/hangzhou_huitian/ht-basic/utils"
	"hisim/global"
	"math/rand"
	"net/http"
	"reflect"
	"slices"
	"time"
)

type Response struct {
	Status int         `json:"status"`
	Text   string      `json:"text"`
	Result interface{} `json:"result"`
}

func PatientCome() { //患者信息
	p := findPatient("out")
	if p == nil {
		return
	}
	cf := global.Conf
	var emp Response
	if err := global.HttpRequest[Response](cf.CollectorAddr+"/patient/info", http.MethodPost, nil, p, &emp); err != nil {
		fmt.Println(err.Error())
		return
	} else {
		if emp.Status != 200 {
			fmt.Println("patient info error:", emp.Text)
		}
	}
}

//患者入院
func AdmitPatient() {
	p := findPatient("out")
	if p == nil {
		return
	}

	findBed := ""

	theArea := tryAndFindItem(allAreas, 5, func(area Area) bool {
		locateBed := findEmptyBedInArea(area.Code)
		findBed = locateBed
		return locateBed != ""
	})

	if theArea.Code == "" {
		return
	}

	p.Area = theArea.Code
	p.Bed = findBed
	p.InDate = JsonTime(time.Now())
	p.VisitNo = GetNewVisitNo(p.PatientId)

	cf := global.Conf
	var emp Response
	if err := global.HttpRequest[Response](cf.CollectorAddr+"/patient/admit", http.MethodPost, nil, p, &emp); err != nil {
		fmt.Println(err.Error())
		return
	} else if emp.Status != 200 {
		fmt.Println(emp.Text)
	} else {
		if err = db.Table("patient").Where("patient_id=?", p.PatientId).Updates(p).Error; err != nil {
			fmt.Println(err.Error())
		} else {
			fmt.Println("patient admit", p.PatientId, p.VisitNo)
		}
	}
}

//患者出院
func PatientOut() {
	p := findPatient("in")
	if p == nil {
		return
	}

	cf := global.Conf
	var emp Response
	if err := global.HttpRequest[Response](cf.CollectorAddr+"/patient/out", http.MethodGet, map[string]string{"visitNo": p.VisitNo}, nil, &emp); err != nil {
		fmt.Println(err.Error())
		return
	} else if emp.Status != 200 {
		fmt.Println("out resp:", p.VisitNo, p.PatientId, "err:"+emp.Text)
	} else if err = db.Table("patient").Where("patient_id=?", p.PatientId).Updates(map[string]interface{}{"area": "", "bed": "", "visit_no": ""}).Error; err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("patient out", p.VisitNo, p.PatientId)
	}
}

func findEmptyBedInArea(area string) string {
	bedInUse := findAllBedInUse(area)
	emptyBeds := make([]string, 0)
	for i := 0; i < 50; i++ {
		sBed := fmt.Sprintf("%02d", i+1)
		if slices.Index(bedInUse, sBed) == -1 {
			emptyBeds = append(emptyBeds, sBed)
		}
	}
	if len(emptyBeds) == 0 {
		return ""
	}
	return randomItem(emptyBeds)
}

func GetNewVisitNo(patId string) string {
	n := ra.Intn(999)
	return fmt.Sprintf(patId+"-%d", n+1)
}

func findPatient(inState string) *Patient {
	gdb := db.Table("patient")
	if inState == "in" {
		gdb.Where("LENGTH(visit_no)>0")
	} else if inState == "out" {
		gdb.Where("visit_no is null or LENGTH(visit_no)=0")
	}

	var pids []string
	var pid string
	if err := gdb.Pluck("patient_id", &pids).Error; err != nil {
		fmt.Println("Pluck patient:", pids)
		return nil
	} else if len(pids) == 0 {
		fmt.Println("no patient:" + inState)
		return nil
	} else {
		if len(pids) <= 2 {
			pid = pids[0]
		} else {
			n := ra.Intn(len(pids))
			pid = pids[n]
		}
	}

	if "" == pid {
		fmt.Println("no patient:" + inState)
		return nil
	}

	var p *Patient
	if err := db.Table("patient").Where("patient_id=?", pid).First(&p).Error; err != nil {
		return nil
	}
	return p
}

func findAllBedInUse(area string) []string {
	var beds []string
	if err := db.Table("patient").Where("area=?", area).Pluck("bed", &beds).Error; err != nil {
		return []string{}
	} else {
		return beds
	}
}

func PatientTransfer() {
	inPatient := findPatient("in")
	if inPatient == nil {
		return
	}

	type AreaPatientCount struct {
		Count int    `gorm:"column:c"`
		Area  string `gorm:"column:area"`
	}

	var acList []*AreaPatientCount
	if err := db.Table("patient").Select("count(*) c,area").Where("visit_no!=''").Group("area").Order("c").Scan(&acList).Error; err != nil {
		fmt.Println(err.Error())
		return
	} else if len(acList) == 0 {
		return
	}

	inArea := tryAndFindItem(acList, 5, func(c *AreaPatientCount) bool {
		return c.Count <= 50
	})
	if inArea == nil {
		fmt.Println("find in area failed!")
		return
	}

	outChoseList := htutil.Filter(acList, func(c *AreaPatientCount) bool {
		return c.Count > 1
	})
	outArea := tryAndFindItem(outChoseList, 5, func(c *AreaPatientCount) bool {
		return c.Area != inArea.Area && c.Count > 1
	})
	if outArea == nil {
		fmt.Println("find out area failed!")
		return
	}
	transferPatient(outArea.Area, inArea.Area)
}

func transferPatient(fromArea string, toArea string) {
	var patList []*Patient
	if err := db.Table("patient").Where("area=? and visit_no!=''", fromArea).Scan(&patList).Error; err != nil {
		fmt.Println(err.Error())
	} else {
		fromP := tryAndFindItem(patList, 5, nil)
		if fromP == nil {
			return
		}

		toBed := findEmptyBedInArea(toArea)
		if toBed == "" {
			return
		}

		transferId := fmt.Sprintf("%s-%d", time.Now().Format("060102"), rand.Intn(9999))
		tran := htbsc.Transfer{
			ID:         0,
			VisitNo:    fromP.VisitNo,
			TransferId: transferId,
			Action:     0,
			ActionTime: htbsc.HtTime{},
			OriginArea: fromArea,
			OriginBed:  fromP.Bed,
			TargetArea: toArea,
			TargetBed:  toBed,
		}

		mapReq := fus.StructToMap(tran, func(path fus.FieldPath, field reflect.StructField) string {
			if field.Name == "ID" || field.Name == "Action" || field.Name == "ActionTime" {
				return ""
			}
			return field.Name
		}, nil)
		//
		cf := global.Conf
		var emp Response
		if err = global.HttpRequest[Response](cf.CollectorAddr+"/patient/transfer", http.MethodPost, nil, mapReq, &emp); err != nil {
			fmt.Println(err.Error())
			return
		} else if emp.Status != 200 {
			fmt.Println("transfer err:"+emp.Text, "| visitNo", fromP.VisitNo, fromArea, fromP.Bed, "==>", toArea, toBed)
		} else if err = db.Table("patient").Where("visit_no=?", fromP.VisitNo).Updates(map[string]interface{}{"area": toArea, "bed": toBed}).Error; err != nil {
			fmt.Println(err.Error())
		} else {
			fmt.Println("patient transfer", fromP.VisitNo, fromP.PatientId, "==>", toArea, toBed)
		}
	}
}

func tryAndFindItem[T interface{}](list []T, tryCount int, fit func(T) bool) T {
	var tryTimes int
	for {
		if tryTimes > tryCount {
			break
		}
		tryTimes++
		a := randomItem(list)
		if fit == nil || fit(a) {
			return a
		}
	}
	var t T
	return t
}

func randomItem[T interface{}](list []T) T {
	var t T

	switch len(list) {
	case 0:
		return t
	case 1:
		return list[0]
	default:
		return list[ra.Intn(len(list))]
	}
}
